<!DOCTYPE html>
<html>

<head>

  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  <title>JWT认证</title>


  <style type="text/css">
    body {
      font-family: Helvetica, arial, sans-serif;
      font-size: 14px;
      line-height: 1.6;
      padding-top: 10px;
      padding-bottom: 10px;
      background-color: white;
      padding: 30px;
    }

    body>*:first-child {
      margin-top: 0 !important;
    }

    body>*:last-child {
      margin-bottom: 0 !important;
    }

    a {
      color: #4183C4;
    }

    a.absent {
      color: #cc0000;
    }

    a.anchor {
      display: block;
      padding-left: 30px;
      margin-left: -30px;
      cursor: pointer;
      position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
    }

    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
      margin: 20px 0 10px;
      padding: 0;
      font-weight: bold;
      -webkit-font-smoothing: antialiased;
      cursor: text;
      position: relative;
    }

    h1:hover a.anchor,
    h2:hover a.anchor,
    h3:hover a.anchor,
    h4:hover a.anchor,
    h5:hover a.anchor,
    h6:hover a.anchor {
      background: url() no-repeat 10px center;
      text-decoration: none;
    }

    h1 tt,
    h1 code {
      font-size: inherit;
    }

    h2 tt,
    h2 code {
      font-size: inherit;
    }

    h3 tt,
    h3 code {
      font-size: inherit;
    }

    h4 tt,
    h4 code {
      font-size: inherit;
    }

    h5 tt,
    h5 code {
      font-size: inherit;
    }

    h6 tt,
    h6 code {
      font-size: inherit;
    }

    h1 {
      font-size: 28px;
      color: black;
    }

    h2 {
      font-size: 24px;
      border-bottom: 1px solid #cccccc;
      color: black;
    }

    h3 {
      font-size: 18px;
    }

    h4 {
      font-size: 16px;
    }

    h5 {
      font-size: 14px;
    }

    h6 {
      color: #777777;
      font-size: 14px;
    }

    p,
    blockquote,
    ul,
    ol,
    dl,
    li,
    table,
    pre {
      margin: 15px 0;
    }

    hr {
      background: transparent url() repeat-x 0 0;
      border: 0 none;
      color: #cccccc;
      height: 4px;
      padding: 0;
    }

    body>h2:first-child {
      margin-top: 0;
      padding-top: 0;
    }

    body>h1:first-child {
      margin-top: 0;
      padding-top: 0;
    }

    body>h1:first-child+h2 {
      margin-top: 0;
      padding-top: 0;
    }

    body>h3:first-child,
    body>h4:first-child,
    body>h5:first-child,
    body>h6:first-child {
      margin-top: 0;
      padding-top: 0;
    }

    a:first-child h1,
    a:first-child h2,
    a:first-child h3,
    a:first-child h4,
    a:first-child h5,
    a:first-child h6 {
      margin-top: 0;
      padding-top: 0;
    }

    h1 p,
    h2 p,
    h3 p,
    h4 p,
    h5 p,
    h6 p {
      margin-top: 0;
    }

    li p.first {
      display: inline-block;
    }

    li {
      margin: 0;
    }

    ul,
    ol {
      padding-left: 30px;
    }

    ul :first-child,
    ol :first-child {
      margin-top: 0;
    }

    dl {
      padding: 0;
    }

    dl dt {
      font-size: 14px;
      font-weight: bold;
      font-style: italic;
      padding: 0;
      margin: 15px 0 5px;
    }

    dl dt:first-child {
      padding: 0;
    }

    dl dt> :first-child {
      margin-top: 0;
    }

    dl dt> :last-child {
      margin-bottom: 0;
    }

    dl dd {
      margin: 0 0 15px;
      padding: 0 15px;
    }

    dl dd> :first-child {
      margin-top: 0;
    }

    dl dd> :last-child {
      margin-bottom: 0;
    }

    blockquote {
      border-left: 4px solid #dddddd;
      padding: 0 15px;
      color: #777777;
    }

    blockquote> :first-child {
      margin-top: 0;
    }

    blockquote> :last-child {
      margin-bottom: 0;
    }

    table {
      padding: 0;
      border-collapse: collapse;
    }

    table tr {
      border-top: 1px solid #cccccc;
      background-color: white;
      margin: 0;
      padding: 0;
    }

    table tr:nth-child(2n) {
      background-color: #f8f8f8;
    }

    table tr th {
      font-weight: bold;
      border: 1px solid #cccccc;
      margin: 0;
      padding: 6px 13px;
    }

    table tr td {
      border: 1px solid #cccccc;
      margin: 0;
      padding: 6px 13px;
    }

    table tr th :first-child,
    table tr td :first-child {
      margin-top: 0;
    }

    table tr th :last-child,
    table tr td :last-child {
      margin-bottom: 0;
    }

    img {
      max-width: 100%;
    }

    span.frame {
      display: block;
      overflow: hidden;
    }

    span.frame>span {
      border: 1px solid #dddddd;
      display: block;
      float: left;
      overflow: hidden;
      margin: 13px 0 0;
      padding: 7px;
      width: auto;
    }

    span.frame span img {
      display: block;
      float: left;
    }

    span.frame span span {
      clear: both;
      color: #333333;
      display: block;
      padding: 5px 0 0;
    }

    span.align-center {
      display: block;
      overflow: hidden;
      clear: both;
    }

    span.align-center>span {
      display: block;
      overflow: hidden;
      margin: 13px auto 0;
      text-align: center;
    }

    span.align-center span img {
      margin: 0 auto;
      text-align: center;
    }

    span.align-right {
      display: block;
      overflow: hidden;
      clear: both;
    }

    span.align-right>span {
      display: block;
      overflow: hidden;
      margin: 13px 0 0;
      text-align: right;
    }

    span.align-right span img {
      margin: 0;
      text-align: right;
    }

    span.float-left {
      display: block;
      margin-right: 13px;
      overflow: hidden;
      float: left;
    }

    span.float-left span {
      margin: 13px 0 0;
    }

    span.float-right {
      display: block;
      margin-left: 13px;
      overflow: hidden;
      float: right;
    }

    span.float-right>span {
      display: block;
      overflow: hidden;
      margin: 13px auto 0;
      text-align: right;
    }

    code,
    tt {
      margin: 0 2px;
      padding: 0 5px;
      white-space: nowrap;
      border: 1px solid #eaeaea;
      background-color: #f8f8f8;
      border-radius: 3px;
    }

    pre code {
      margin: 0;
      padding: 0;
      white-space: pre;
      border: none;
      background: transparent;
    }

    .highlight pre {
      background-color: #f8f8f8;
      border: 1px solid #cccccc;
      font-size: 13px;
      line-height: 19px;
      overflow: auto;
      padding: 6px 10px;
      border-radius: 3px;
    }

    pre {
      background-color: #f8f8f8;
      border: 1px solid #cccccc;
      font-size: 13px;
      line-height: 19px;
      overflow: auto;
      padding: 6px 10px;
      border-radius: 3px;
    }

    pre code,
    pre tt {
      background-color: transparent;
      border: none;
    }

    sup {
      font-size: 0.83em;
      vertical-align: super;
      line-height: 0;
    }

    kbd {
      display: inline-block;
      padding: 3px 5px;
      font-size: 11px;
      line-height: 10px;
      color: #555;
      vertical-align: middle;
      background-color: #fcfcfc;
      border: solid 1px #ccc;
      border-bottom-color: #bbb;
      border-radius: 3px;
      box-shadow: inset 0 -1px 0 #bbb
    }

    * {
      -webkit-print-color-adjust: exact;
    }

    @media screen and (min-width: 914px) {
      body {
        width: 854px;
        margin: 0 auto;
      }
    }

    @media print {

      table,
      pre {
        page-break-inside: avoid;
      }

      pre {
        word-wrap: break-word;
      }
    }
  </style>

  <style type="text/css">
    /**
 * okaidia theme for JavaScript, CSS and HTML
 * Loosely based on Monokai textmate theme by http://www.monokai.nl/
 * @author ocodia
 */

    code[class*="language-"],
    pre[class*="language-"] {
      color: #f8f8f2;
      background: none;
      text-shadow: 0 1px rgba(0, 0, 0, 0.3);
      font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
      text-align: left;
      white-space: pre;
      word-spacing: normal;
      word-break: normal;
      word-wrap: normal;
      line-height: 1.5;

      -moz-tab-size: 4;
      -o-tab-size: 4;
      tab-size: 4;

      -webkit-hyphens: none;
      -moz-hyphens: none;
      -ms-hyphens: none;
      hyphens: none;
    }

    /* Code blocks */
    pre[class*="language-"] {
      padding: 1em;
      margin: .5em 0;
      overflow: auto;
      border-radius: 0.3em;
    }

    :not(pre)>code[class*="language-"],
    pre[class*="language-"] {
      background: #272822;
    }

    /* Inline code */
    :not(pre)>code[class*="language-"] {
      padding: .1em;
      border-radius: .3em;
      white-space: normal;
    }

    .token.comment,
    .token.prolog,
    .token.doctype,
    .token.cdata {
      color: slategray;
    }

    .token.punctuation {
      color: #f8f8f2;
    }

    .namespace {
      opacity: .7;
    }

    .token.property,
    .token.tag,
    .token.constant,
    .token.symbol,
    .token.deleted {
      color: #f92672;
    }

    .token.boolean,
    .token.number {
      color: #ae81ff;
    }

    .token.selector,
    .token.attr-name,
    .token.string,
    .token.char,
    .token.builtin,
    .token.inserted {
      color: #a6e22e;
    }

    .token.operator,
    .token.entity,
    .token.url,
    .language-css .token.string,
    .style .token.string,
    .token.variable {
      color: #f8f8f2;
    }

    .token.atrule,
    .token.attr-value,
    .token.function {
      color: #e6db74;
    }

    .token.keyword {
      color: #66d9ef;
    }

    .token.regex,
    .token.important {
      color: #fd971f;
    }

    .token.important,
    .token.bold {
      font-weight: bold;
    }

    .token.italic {
      font-style: italic;
    }

    .token.entity {
      cursor: help;
    }
  </style>


</head>

<body>

  <h1 id="toc_0">JWT</h1>


  <h2 id="toc_2">Token 认证</h2>

  <p>Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证，服务端认证成功，那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token
    在服务端持久化（比如存入数据库），那它就是一个永久的身份令牌。</p>

  <p>而为了安全考虑 Token 都需要设有效期。一般有效期越短越好，但也要符合用户习惯。如果用户在正常操作的过程中，Token 过期失效了，要求用户重新登录。为了解决在操作过程不能让用户感到 Token
    失效这个问题，有一种方案是在服务器端保存 Token 状态，用户每次操作都会自动刷新（推迟） Token 的过期时间——Session 就是采用这种策略来保持用户登录状态的。然而仍然存在这样一个问题，在前后端分离、单页 App
    这些情况下，每秒种可能发起很多次请求，每次都去刷新过期时间会产生非常大的代价。如果 Token 的过期时间被持久化到数据库或文件，代价就更大了。所以通常为了提升效率，减少消耗，会把 Token 的过期时保存在缓存或者内存中。</p>

  <p>还有另一种方案，使用 Refresh Token，它可以避免频繁的读写操作。这种方案中，服务端不需要刷新 Token 的过期时间，一旦 Token 过期，就反馈给前端，前端使用 Refresh Token 申请一个全新 Token
    继续使用。这种方案中，服务端只需要在客户端请求更新 Token 的时候对 Refresh Token 的有效性进行一次检查，大大减少了更新有效期的操作，也就避免了频繁读写。当然 Refresh Token
    也是有有效期的，但是这个有效期就可以长一点了，比如，以天为单位的时间。</p>

  <h2 id="toc_3">JWT的构成</h2>

  <p>JWT（json web token）是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准。一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息，以便于从资源服务器获取资源，比如用在用户登录上。</p>

  <p>在传统的用户登录认证中，因为 http 是无状态的，所以都是采用 session 方式。用户登录成功，服务端会保证一个 session，当然会给客户端一个 sessionId，客户端会把 sessionId 保存在 cookie
    中，每次请求都会携带这个 sessionId。</p>

  <p>而 JWT 只需要服务端生成 token，客户端保存这个 token，每次请求携带这个 token，服务端认证解析就可。</p>

  <h4 id="toc_4">JWT生成编码后的样子：</h4>

  <div>
    <pre><code class="language-none">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiend6IiwiYWdlIjoiMTgifQ.UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q</code></pre>
  </div>

  <p>从上面可以看出 JWT 由三部分组件，分别是 头部（header),载荷（payload) 以及 签证（signature)。</p>

  <h3 id="toc_5">header</h3>

  <ul>
    <li>令牌类型：如 jwt；</li>
    <li>加密的算法：默认为 HMAC SHA256（HS256）或者 RSA。</li>
  </ul>

  <p>完整的头部就像是下面这样的一个 JSON 对象：</p>

  <div>
    <pre><code class="language-javascript">{
  &#39;typ&#39;: &#39;JWT&#39;,
  &#39;alg&#39;: &#39;HS256&#39;
}</code></pre>
  </div>

  <p>然后将头部进行 base64 加密（该加密是可以对称解密的),构成了第一部分：</p>

  <div>
    <pre><code class="language-none">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</code></pre>
  </div>

  <h3 id="toc_6">playload</h3>

  <p>载荷就是存放有效信息的地方。总共提供了七个可选字段：</p>

  <ul>
    <li>iss: jwt 签发者</li>
    <li>sub: jwt 所面向的用户</li>
    <li>aud: 接收 jwt 的一方</li>
    <li>exp: jwt 的过期时间，这个过期时间必须要大于签发时间</li>
    <li>nbf: 生效时间</li>
    <li>iat: jwt 的签发时间</li>
    <li>jti: 编号，jwt 的唯一身份标识，主要用来作为一次性 token ,从而回避重放攻击</li>
  </ul>

  <p>除了这几个可选字段外，我们还可以在这个部分定义私有字段：</p>

  <div>
    <pre><code class="language-javascript">{
  &quot;sub&quot;: &quot;admin&quot;,
  &quot;exp&quot;: 1441594722,
  &quot;name&quot;: &quot;zf&quot;,
  &quot;admin&quot;: true
}</code></pre>
  </div>

  <p>然后将其进行 base64 加密，得到 JWT 的第二部分：</p>

  <div>
    <pre><code class="language-none">eyJuYW1lIjoiend6IiwiYWdlIjoiMTgifQ</code></pre>
  </div>

  <blockquote>
    <p>注意：JWT 默认是不加密的，任何人都可以读到，所以不要把秘密信息放在这个部分。</p>
  </blockquote>

  <h3 id="toc_7">signature</h3>

  <p>这个部分是对前两个部分进行 base64 加密，再用 Header 里面指定的签名算法加密，加密的时候要指定一个 Secret ，这个相当于是一个密码，该密码存储在服务端，不能泄露给用户用于防止数据篡改。</p>

  <p>最后把 Header、Payload、Signature 三个部分拼成一个字符串，每个部分之间用 <code>.</code> 分隔，然后返回给用户：</p>

  <div>
    <pre><code class="language-javascript">var encodedString = base64UrlEncode(header) + &quot;.&quot; + base64UrlEncode(payload); 
HMACSHA256(encodedString, &#39;secret&#39;);</code></pre>
  </div>

  <div>
    <pre><code class="language-bash"># 处理完成以后看起来像这样：
UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q</code></pre>
  </div>

  <p>客户端收到这个 Token 以后把它存储下来，下回向服务端发送请求的时候就带着这个 Token 。服务端收到这个 Token ，然后进行验证，通过以后就会返回给客户端想要的资源。</p>

  <h2 id="toc_8">基于 Token 的身份验证方法</h2>

  <h3 id="toc_9">流程：</h3>

  <ul>
    <li>客户端使用用户名跟密码请求登录</li>
    <li>服务端收到请求，去验证用户名与密码</li>
    <li>验证成功后，服务端会签发一个 Token，再把这个 Token 发送给客户端</li>
    <li>客户端收到 Token 以后可以把它存储起来，比如放在 Cookie 里或者 Local Storage 里</li>
    <li>客户端每次向服务端请求资源的时候需要带着服务端签发的 Token</li>
    <li>服务端收到请求，然后去验证客户端请求里面带着的 Token，如果验证成功，就向客户端返回请求的数据</li>
  </ul>

  <p>如果想自己生成 JWT 会有点复杂，所以可以直接利用第三方库来支持 JWT。如 ： jsonwebtoken。</p>

  <h3 id="toc_10">jsonwebtoken 的使用</h3>

  <div>
    <pre><code class="language-bash"># 安装
npm install jsonwebtoken</code></pre>
  </div>

  <p>jsonwebtoken 主要提供两个方法：</p>

  <ul>
    <li>
      <p>sign 用于生成 token</p>
    </li>
    <li>
      <p>verify 用于检验 token</p>
    </li>
  </ul>

  <h4 id="toc_11">sign</h4>

  <div>
    <pre><code class="language-javascript">var jwt = require(&#39;jsonwebtoken&#39;);
// 语法 
jwt.sign(payload, secretOrPrivateKey, [options, callback])</code></pre>
  </div>

  <ul>
    <li>
      <p>payload：必须是一个 object, buffer 或者 string。请注意， exp 只有当 payload 是 object 字面量时才可以设置。</p>
    </li>
    <li>
      <p>secretOrPrivateKey：是包含 HMAC 算法的密钥或 RSA 和 ECDSA 的 PEM 编码私钥的 string 或 buffer。</p>
    </li>
    <li>
      <p>options 为配置对象：</p>

      <ul>
        <li>algorithm: 加密算法，默认值 HS256</li>
        <li>expiresIn：过期时间，如果值为数值，则默认为多少秒，如果值为字符串，请提供时间单位（&quot;2 days&quot;, &quot;10h&quot;, &quot;7d&quot;），否则默认为毫秒，如
          &quot;120&quot; 等于“120ms”</li>
      </ul>
    </li>
  </ul>

  <p>在 expiresIn，notBefore，audience，subject，issuer 没有默认值时。也可以直接在 payload 中用 exp，nbf，aud，sub 和 iss 分别表示，但是你不能在这两个地方同时设置。
  </p>

  <h4 id="toc_12">verify</h4>

  <p>验证 token 的合法性。</p>

  <div>
    <pre><code class="language-javascript">jwt.verify（token，secretOrPublicKey，[options，callback]）</code></pre>
  </div>

  <p>但是 jsonwebtoken 提供的 verify 比较简单，如果想要实现更加复杂的验证，可以通过 express-jwt 来实现。</p>

  <h3 id="toc_13">express-jwt</h3>

  <p>验证指定 http 请求的 JsonWebTokens 的有效性。</p>

  <div>
    <pre><code class="language-bash">npm install express-jwt</code></pre>
  </div>

  <div>
    <pre><code class="language-javascript">var jwt = require(&#39;express-jwt&#39;);

app.use(jwt({ 
  secret: &#39;secretOrPrivateKey&#39; // 加密 token 的秘钥
}).unless({ // 选择一些不受保护的路径
  path: [&#39;/token&#39;]
}));</code></pre>
  </div>

  <p>jsonwebtoken 是用来生成 token 给客户端的，express-jwt 是用来验证token的。</p>

  <h4 id="toc_14">校验 token 失败时的处理</h4>

  <div>
    <pre><code class="language-javascript">app.use(function (err, req, res, next) {
  if (err.name === &#39;UnauthorizedError&#39;) {   
      // 具体的 err 值 请看下面
    res.status(401).send(&#39;invalid token...&#39;);
  }
});

// token 过期时的 err 值：
{
    &quot;name&quot;: &quot;UnauthorizedError&quot;,
    &quot;message&quot;: &quot;jwt expired&quot;,
    &quot;code&quot;: &quot;invalid_token&quot;,
    &quot;status&quot;: 401,
    &quot;inner&quot;: {
        &quot;name&quot;: &quot;TokenExpiredError&quot;,
        &quot;message&quot;: &quot;jwt expired&quot;,
        &quot;expiredAt&quot;: &quot;2017-08-03T10:08:44.000Z&quot;
    }
}

// token 无效时的 err 值：
{
    &quot;name&quot;: &quot;UnauthorizedError&quot;,
    &quot;message&quot;: &quot;invalid signature&quot;,
    &quot;code&quot;: &quot;invalid_token&quot;,
    &quot;status&quot;: 401,
    &quot;inner&quot;: {
        &quot;name&quot;: &quot;JsonWebTokenError&quot;,
        &quot;message&quot;: &quot;invalid signature&quot;
    }
}</code></pre>
  </div>

  <h3 id="toc_15">express 中使用</h3>

  <div>
    <pre><code class="language-bash"># 安装
npm install express-jwt --save
npm install jsonwebtoken --save</code></pre>
  </div>

  <div>
    <pre><code class="language-javascript">//app.js 配置
const express = require(&#39;express&#39;);
const ejs = require(&#39;ejs&#39;);
const path = require(&#39;path&#39;);
const bodyParser = require(&#39;body-parser&#39;);
const logger = require(&#39;morgan&#39;);

const app = express();

const routes = require(&#39;./routes&#39;);

// 模版引擎设置
app.set(&#39;views&#39;, path.join(__dirname, &#39;views&#39;));
app.set(&#39;view engine&#39;, &#39;ejs&#39;);

// 每一次服务请求都会将信息打印在控制台中
app.use(logger(&#39;dev&#39;));

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//设置静态资源
app.use(express.static(path.join(__dirname, &#39;public&#39;)));

routes(app); // 路由引入

app.listen(3000);</code></pre>
  </div>

  <div>
    <pre><code class="language-javascript">// routes/index.js 配置
const expressJWT = require(&#39;express-jwt&#39;);
const util = require(&#39;../util/util&#39;);

module.exports = (app) =&gt; {
  // 设置需要保护的 API，这里需要设置 secret 作为秘钥
  app.use(expressJWT({
    secret: util.secretOrPrivateKey,
    algorithms: ['HS256'] // express-jwt 6.x 版本后需要设置算法，否则会报 algorithms should be set 的错误
  }).unless({
    path: [&#39;/login&#39;]  //除了这个地址，其他的URL都需要验证
  }));

  app.get(&#39;/&#39;, (req, res) =&gt; {
    res.json({ message: &#39;hello!&#39;});
  });

  app.use(&#39;/login&#39;, require(&#39;./login&#39;));
  app.use(&#39;/list&#39;, require(&#39;./list&#39;));

  // 404
  app.use(function(req, res, next) {
    next(createError(404));
  });

  // 5错误
  app.use(function(err, req, res, next) {
    //校验 token 失败时的处理
    // 默认的情况下，解析 JWT 失败会抛出异常，可以通过以下设置来处理该异常
    if (err.name === &#39;UnauthorizedError&#39;) {   
      // 这个需要根据自己的业务逻辑来处理
      res.status(401).json({
        message: &#39;invalid token&#39;,
        error: err
      });
      return;
    }

    res.status(err.status || 500).json({
      message: err.message,
      error: err
    });
  });
};</code></pre>
  </div>

  <div>
    <pre><code class="language-javascript">// routes/login.js
const express = require(&#39;express&#39;);
const jwt = require(&#39;jsonwebtoken&#39;); // 引入 jsonwebtoken
const util = require(&#39;../util/util&#39;);
const router = express.Router();

//模拟用户数据
let user = {
  name:&#39;admin&#39;,
  pass:&#39;123&#39;,
  role:0
};

router.post(&#39;/&#39;, function(req, res) {
  console.log(req.body);
  if(req.body.name === user.name &amp;&amp; req.body.pass === user.pass) {
    console.log(&#39;账号正确&#39;);

    const token = jwt.sign({
      user_name: req.body.name
    }, util.secretOrPrivateKey , { // 秘钥
        expiresIn: 60 * 60 // 过期时间
    });

    res.json({
      msg:&#39;登陆成功&#39;,
      token:token
    });
  } else {
    res.json({
      msg:&#39;登录失败&#39;
    });
  }
});

module.exports = router;</code></pre>
  </div>

  <p>前端拿到 token 后可以直接存储在 localStorage 中，然后每次请求的时候挂载 Authorization：</p>

  <div>
    <pre><code class="language-javascript">const token = localStorage.getItem(&#39;token&#39;);
axios.get(`/home`, {
  headers: {
    Authorization: Bearer &#39; + token
  }
})</code></pre>
  </div>

  <p>封装 axios 请求：</p>

  <div>
    <pre><code class="language-javascript">// http.js
import axios from &#39;axios&#39;;
import { createBrowserHistory } from &#39;history&#39;;

// 创建历史对象
const history = createBrowserHistory();

// 添加一个新的 axios 实例
var http = axios.create();

// 拦截请求，给所有的请求都带上 token
http.interceptors.request.use(
  config =&gt; {
    const token = localStorage.getItem(&#39;token&#39;);
    if (token) {
      // 设置 token ，一般是在 headers 里加入 Authorization，并加上 Bearer 标注
      // 最好通过此种形式设置 request.headers[&#39;Authorization&#39;]
      config.headers[&#39;Authorization&#39;] = &#39;Bearer &#39; + token;
    }
    return config;
  },
  error =&gt; {
    console.log(error);
    return Promise.reject(error);
});

// 拦截响应，遇到 token 不合法则报错
http.interceptors.response.use(
  response =&gt; {
    if (response.data.token) {
      localStorage.setItem(&#39;token&#39;, response.data.token);
    }
    return response;
  },
  error =&gt; {
    if (error.response.status === 401) {
      // 401 说明 token 验证失败
      // 可以直接跳转到登录页面，重新登录获取 token
      localStorage.removeItem(&#39;token&#39;);
      console.log(error.response.data.error.message);
      history.replace(&#39;/login&#39;);
    } else if(error.response.status === 500) {
      // 服务器错误
      return Promise.reject(&#39;服务器出错：&#39;,error.response.data);
    }
    return Promise.reject(error.response.data);   // 返回接口返回的错误信息
  });

export default http;</code></pre>
  </div>

  <p>react 引用：</p>

  <div>
    <pre><code class="language-javascript">home.js
import React, { Component } from &#39;react&#39;;
import http from &#39;./http&#39;;

export default class Home extends Component{
  componentDidMount() {
    let that = this;
    http.get(url)
    .then(response =&gt; {
      //...
    })
    .catch((err) =&gt; {
      console.log(err);
    });
  }
  render() {
    return (
      //...
    )
  }
}</code></pre>
  </div>



  <script type="text/javascript">
    var _self = "undefined" != typeof window ? window : "undefined" != typeof WorkerGlobalScope && self instanceof WorkerGlobalScope ? self : {}, Prism = function () { var e = /\blang(?:uage)?-(\w+)\b/i, t = 0, n = _self.Prism = { util: { encode: function (e) { return e instanceof a ? new a(e.type, n.util.encode(e.content), e.alias) : "Array" === n.util.type(e) ? e.map(n.util.encode) : e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/\u00a0/g, " ") }, type: function (e) { return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1] }, objId: function (e) { return e.__id || Object.defineProperty(e, "__id", { value: ++t }), e.__id }, clone: function (e) { var t = n.util.type(e); switch (t) { case "Object": var a = {}; for (var r in e) e.hasOwnProperty(r) && (a[r] = n.util.clone(e[r])); return a; case "Array": return e.map && e.map(function (e) { return n.util.clone(e) }) }return e } }, languages: { extend: function (e, t) { var a = n.util.clone(n.languages[e]); for (var r in t) a[r] = t[r]; return a }, insertBefore: function (e, t, a, r) { r = r || n.languages; var l = r[e]; if (2 == arguments.length) { a = arguments[1]; for (var i in a) a.hasOwnProperty(i) && (l[i] = a[i]); return l } var o = {}; for (var s in l) if (l.hasOwnProperty(s)) { if (s == t) for (var i in a) a.hasOwnProperty(i) && (o[i] = a[i]); o[s] = l[s] } return n.languages.DFS(n.languages, function (t, n) { n === r[e] && t != e && (this[t] = o) }), r[e] = o }, DFS: function (e, t, a, r) { r = r || {}; for (var l in e) e.hasOwnProperty(l) && (t.call(e, l, e[l], a || l), "Object" !== n.util.type(e[l]) || r[n.util.objId(e[l])] ? "Array" !== n.util.type(e[l]) || r[n.util.objId(e[l])] || (r[n.util.objId(e[l])] = !0, n.languages.DFS(e[l], t, l, r)) : (r[n.util.objId(e[l])] = !0, n.languages.DFS(e[l], t, null, r))) } }, plugins: {}, highlightAll: function (e, t) { var a = { callback: t, selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code' }; n.hooks.run("before-highlightall", a); for (var r, l = a.elements || document.querySelectorAll(a.selector), i = 0; r = l[i++];)n.highlightElement(r, e === !0, a.callback) }, highlightElement: function (t, a, r) { for (var l, i, o = t; o && !e.test(o.className);)o = o.parentNode; o && (l = (o.className.match(e) || [, ""])[1], i = n.languages[l]), t.className = t.className.replace(e, "").replace(/\s+/g, " ") + " language-" + l, o = t.parentNode, /pre/i.test(o.nodeName) && (o.className = o.className.replace(e, "").replace(/\s+/g, " ") + " language-" + l); var s = t.textContent, u = { element: t, language: l, grammar: i, code: s }; if (!s || !i) return n.hooks.run("complete", u), void 0; if (n.hooks.run("before-highlight", u), a && _self.Worker) { var c = new Worker(n.filename); c.onmessage = function (e) { u.highlightedCode = e.data, n.hooks.run("before-insert", u), u.element.innerHTML = u.highlightedCode, r && r.call(u.element), n.hooks.run("after-highlight", u), n.hooks.run("complete", u) }, c.postMessage(JSON.stringify({ language: u.language, code: u.code, immediateClose: !0 })) } else u.highlightedCode = n.highlight(u.code, u.grammar, u.language), n.hooks.run("before-insert", u), u.element.innerHTML = u.highlightedCode, r && r.call(t), n.hooks.run("after-highlight", u), n.hooks.run("complete", u) }, highlight: function (e, t, r) { var l = n.tokenize(e, t); return a.stringify(n.util.encode(l), r) }, tokenize: function (e, t) { var a = n.Token, r = [e], l = t.rest; if (l) { for (var i in l) t[i] = l[i]; delete t.rest } e: for (var i in t) if (t.hasOwnProperty(i) && t[i]) { var o = t[i]; o = "Array" === n.util.type(o) ? o : [o]; for (var s = 0; s < o.length; ++s) { var u = o[s], c = u.inside, g = !!u.lookbehind, h = !!u.greedy, f = 0, d = u.alias; u = u.pattern || u; for (var p = 0; p < r.length; p++) { var m = r[p]; if (r.length > e.length) break e; if (!(m instanceof a)) { u.lastIndex = 0; var y = u.exec(m), v = 1; if (!y && h && p != r.length - 1) { var b = r[p + 1].matchedStr || r[p + 1], k = m + b; if (p < r.length - 2 && (k += r[p + 2].matchedStr || r[p + 2]), u.lastIndex = 0, y = u.exec(k), !y) continue; var w = y.index + (g ? y[1].length : 0); if (w >= m.length) continue; var _ = y.index + y[0].length, P = m.length + b.length; if (v = 3, P >= _) { if (r[p + 1].greedy) continue; v = 2, k = k.slice(0, P) } m = k } if (y) { g && (f = y[1].length); var w = y.index + f, y = y[0].slice(f), _ = w + y.length, S = m.slice(0, w), O = m.slice(_), j = [p, v]; S && j.push(S); var A = new a(i, c ? n.tokenize(y, c) : y, d, y, h); j.push(A), O && j.push(O), Array.prototype.splice.apply(r, j) } } } } } return r }, hooks: { all: {}, add: function (e, t) { var a = n.hooks.all; a[e] = a[e] || [], a[e].push(t) }, run: function (e, t) { var a = n.hooks.all[e]; if (a && a.length) for (var r, l = 0; r = a[l++];)r(t) } } }, a = n.Token = function (e, t, n, a, r) { this.type = e, this.content = t, this.alias = n, this.matchedStr = a || null, this.greedy = !!r }; if (a.stringify = function (e, t, r) { if ("string" == typeof e) return e; if ("Array" === n.util.type(e)) return e.map(function (n) { return a.stringify(n, t, e) }).join(""); var l = { type: e.type, content: a.stringify(e.content, t, r), tag: "span", classes: ["token", e.type], attributes: {}, language: t, parent: r }; if ("comment" == l.type && (l.attributes.spellcheck = "true"), e.alias) { var i = "Array" === n.util.type(e.alias) ? e.alias : [e.alias]; Array.prototype.push.apply(l.classes, i) } n.hooks.run("wrap", l); var o = ""; for (var s in l.attributes) o += (o ? " " : "") + s + '="' + (l.attributes[s] || "") + '"'; return "<" + l.tag + ' class="' + l.classes.join(" ") + '" ' + o + ">" + l.content + "</" + l.tag + ">" }, !_self.document) return _self.addEventListener ? (_self.addEventListener("message", function (e) { var t = JSON.parse(e.data), a = t.language, r = t.code, l = t.immediateClose; _self.postMessage(n.highlight(r, n.languages[a], a)), l && _self.close() }, !1), _self.Prism) : _self.Prism; var r = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop(); return r && (n.filename = r.src, document.addEventListener && !r.hasAttribute("data-manual") && document.addEventListener("DOMContentLoaded", n.highlightAll)), _self.Prism }(); "undefined" != typeof module && module.exports && (module.exports = Prism), "undefined" != typeof global && (global.Prism = Prism);
  </script>

  <script type="text/javascript">
    Prism.languages.clike = { comment: [{ pattern: /(^|[^\\])\/\*[\w\W]*?\*\//, lookbehind: !0 }, { pattern: /(^|[^\\:])\/\/.*/, lookbehind: !0 }], string: { pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, greedy: !0 }, "class-name": { pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i, lookbehind: !0, inside: { punctuation: /(\.|\\)/ } }, keyword: /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/, "boolean": /\b(true|false)\b/, "function": /[a-z0-9_]+(?=\()/i, number: /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i, operator: /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/, punctuation: /[{}[\];(),.:]/ };
  </script>

  <script type="text/javascript">
    Prism.languages.javascript = Prism.languages.extend("clike", { keyword: /\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/, number: /\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/, "function": /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i }), Prism.languages.insertBefore("javascript", "keyword", { regex: { pattern: /(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/, lookbehind: !0, greedy: !0 } }), Prism.languages.insertBefore("javascript", "class-name", { "template-string": { pattern: /`(?:\\\\|\\?[^\\])*?`/, greedy: !0, inside: { interpolation: { pattern: /\$\{[^}]+\}/, inside: { "interpolation-punctuation": { pattern: /^\$\{|\}$/, alias: "punctuation" }, rest: Prism.languages.javascript } }, string: /[\s\S]+/ } } }), Prism.languages.markup && Prism.languages.insertBefore("markup", "tag", { script: { pattern: /(<script[\w\W]*?>)[\w\W]*?(?=<\/script>)/i, lookbehind: !0, inside: Prism.languages.javascript, alias: "language-javascript" } }), Prism.languages.js = Prism.languages.javascript;
  </script>

  <script type="text/javascript">
    !function (e) { var t = { variable: [{ pattern: /\$?\(\([\w\W]+?\)\)/, inside: { variable: [{ pattern: /(^\$\(\([\w\W]+)\)\)/, lookbehind: !0 }, /^\$\(\(/], number: /\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee]-?\d+)?)\b/, operator: /--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/, punctuation: /\(\(?|\)\)?|,|;/ } }, { pattern: /\$\([^)]+\)|`[^`]+`/, inside: { variable: /^\$\(|^`|\)$|`$/ } }, /\$(?:[a-z0-9_#\?\*!@]+|\{[^}]+\})/i] }; e.languages.bash = { shebang: { pattern: /^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/, alias: "important" }, comment: { pattern: /(^|[^"{\\])#.*/, lookbehind: !0 }, string: [{ pattern: /((?:^|[^<])<<\s*)(?:"|')?(\w+?)(?:"|')?\s*\r?\n(?:[\s\S])*?\r?\n\2/g, lookbehind: !0, inside: t }, { pattern: /(["'])(?:\\\\|\\?[^\\])*?\1/g, inside: t }], variable: t.variable, "function": { pattern: /(^|\s|;|\||&)(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|\s|;|\||&)/, lookbehind: !0 }, keyword: { pattern: /(^|\s|;|\||&)(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|\s|;|\||&)/, lookbehind: !0 }, "boolean": { pattern: /(^|\s|;|\||&)(?:true|false)(?=$|\s|;|\||&)/, lookbehind: !0 }, operator: /&&?|\|\|?|==?|!=?|<<<?|>>|<=?|>=?|=~/, punctuation: /\$?\(\(?|\)\)?|\.\.|[{}[\];]/ }; var a = t.variable[1].inside; a["function"] = e.languages.bash["function"], a.keyword = e.languages.bash.keyword, a.boolean = e.languages.bash.boolean, a.operator = e.languages.bash.operator, a.punctuation = e.languages.bash.punctuation }(Prism);
  </script>


</body>

</html>